﻿using System;
using System.Collections.Generic;
using System.IO;
using CtefCrmImport.Common.Interfaces;
using CtefCrmImport.Common.Enums;
using CtefCrmImport.Common.Extensions;
using CtefCrmImport.Common.Models;
using Microsoft.Crm.Sdk.Samples;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System.Linq;

namespace CtefCrmImport.Common.Operations
{
    public class ImportDonations
    {
        private const string DefaultCurrencyCode = "USD";

        private CommonOperations common;
        private ILogger logger;
        private IEntityBuilder<Donation> builder;
        private IDictionary<string, EntityReferenceCollection> fullNameContactMap;
        private IDictionary<string, EntityReferenceCollection> emailContactMap;
        private HashSet<string> transactionIds;
        private EntityReferenceCollection defaultCurrency;

        private ServerConnection.Configuration config;

        public ImportDonations(ILogger logger, ServerConnection.Configuration config, IEntityBuilder<Donation> donationBuilder)
        {
            this.logger = logger;
            common = new CommonOperations(logger);
            this.config = config;
            this.builder = donationBuilder;
        }


        public void Run(string filePath, int headerRowCount)
        {
            logger.Log("Start processing...");

            var donations = new FileParser<Donation>(filePath, headerRowCount, logger, builder).ParseEntities();

            using (var serviceProxy = new OrganizationServiceProxy(config.OrganizationUri,
                                                                   config.HomeRealmUri,
                                                                   config.Credentials,
                                                                   config.DeviceCredentials))
            {
                // This statement is required to enable early-bound type support.
                serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());

                IOrganizationService service = serviceProxy;

                common.CheckVersion(service);

                defaultCurrency = common.GetCurrency(service, DefaultCurrencyCode);
                if (defaultCurrency.IsNullOrEmpty())
                {
                    throw new InvalidDataException("Cannot find default currency!");
                }

                transactionIds = common.BuildTransactions(service);
                common.BuildContactMaps(service, out emailContactMap, out fullNameContactMap);

                CreateDonations(service, donations);
            }

            logger.Flush();
        }

        private void CreateDonations(IOrganizationService service, IEnumerable<Donation> donations)
        {
            foreach (var d in donations)
            {
                var donorRef = common.FindDonor(d.From, emailContactMap, fullNameContactMap);

                if (donorRef == null)
                {
                    common.ImportDonors(service, new[] { d.From }, emailContactMap, fullNameContactMap);
                    donorRef = common.FindDonor(d.From, emailContactMap, fullNameContactMap);
                }

                var fullName = d.From.FullName;
                if (1 < donorRef.Count)
                {
                    logger.Log("Warning: ambiguous donor name {0}. Found {1} matches. Please resolve this entry manually.", fullName, donorRef.Count);
                    logger.Log("Original input: {0}", d.LineInput);
                    logger.Log(Environment.NewLine);
                    continue;
                }

                if (HasMatchingDonation(service, donorRef.First(), d))
                {
                    logger.Log("Warning: donation already seems to exist:");
                    logger.Log(d.ToString());
                    logger.Log("Warning: The above record was not imported. Please ignore if it's a duplicate record. Otherwise please enter this data manually in CRM.");
                    continue;
                }

                var donation = new Entity("new_donation");

                var dName = string.IsNullOrWhiteSpace(d.Purpose) ?
                    string.Format("Imported donation from {0} at {1}", fullName, DateTime.Now) :
                    donation["new_name"] = string.Format("Imported donation from {0} for {1}", fullName, d.Purpose);

                // Populate fields
                donation["new_name"] = dName;
                donation["new_date"] = d.Date;
                donation["new_amount"] = new Money { Value = d.GiftAmount };
                donation["new_microsoftmatchamount"] = new Money { Value = d.CompanyMatch };
                donation["new_totalamount"] = new Money { Value = d.TotalAmount };
                donation["new_additionalinfo"] = d.AdditionalInfo;
                donation["new_type"] = new OptionSetValue((int)d.FundType);

                if (d.PaymentInstrument != PaymentType.Unspecified)
                {
                    donation["new_paymentmethod"] = new OptionSetValue((int)d.PaymentInstrument);
                }

                if (!string.IsNullOrWhiteSpace(d.TransactionId))
                {
                    if (transactionIds.Contains(d.TransactionId))
                    {
                        logger.Log("Warning: Transaction id {0} already exists. This record will not be imported:", d.TransactionId);
                        logger.Log(d.LineInput);
                        continue;
                    }
                    donation["new_transactionid"] = d.TransactionId;
                }

                // Associate donation with a single donor
                var id = service.Create(donation);
                var rel = new Relationship("new_contact_new_donation_Donor");
                service.Associate("new_donation", id, rel, donorRef);

                // Associate donation with default currency (USD)
                rel = new Relationship("TransactionCurrency_new_donation");
                service.Associate("new_donation", id, rel, defaultCurrency);

                logger.Log("Successfully imported donation: name = {0}, id = {1}", dName, id);
            }
        }

        private bool HasMatchingDonation(IOrganizationService service, EntityReference donor, Donation donation)
        {
            var donations = common.GetDonationsByDonorId(service, donor.Id);

            return donations.Any(d => d.ToDonation().Matches(donation));
        }
    }
}
